﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Task_When_Scheduling.cs" company="JDML Consulting Ltd.">
//   Copyright 2011 Photon Project - http://photon.codeplex.com
// </copyright>
// <summary>
//   Verifies the threads used during Task workflow.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Photon.Threading
{
    using System;
    using System.Diagnostics;
    using System.Threading;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Mocks;
    using Testing.Threading;

    /// <summary>
   /// Verifies the threads used during Task workflow.
   /// </summary>
   /// <remarks>
   /// The following abreviations are used for threads:
   /// 
   /// TTD : Test Thread Dispatcher - Simulates UI Dispatching
   /// TP  : Thread Pool
   /// </remarks>
   [TestClass]
   public class Task_When_Scheduling : AsynchronousTest
   {
      [TestMethod, AsynchronousTest]
      public void If_Async_UISendAndReceive_From_TTD_Should_Follow_Sequence_TTD_TP_TP_TTD()
      {
         EnqueueAction(() =>
                       RunSchedulerScenarioFromDispatcher(new TaskFactory(TaskScheduler.ForUISendAndReceive()),
                          true,
                          TestThreadType.TestThreadDispatcher,
                          TestThreadType.ThreadPool,
                          TestThreadType.ThreadPool,
                          TestThreadType.TestThreadDispatcher));
      }

      [TestMethod, AsynchronousTest]
      public void If_Sync_UISendAndReceive_From_TTD_Should_Follow_Sequence_TTD_TTD_TTD_TTD()
      {
         EnqueueAction(() =>
                       RunSchedulerScenarioFromDispatcher(new TaskFactory(TaskScheduler.ForUISendAndReceive()),
                          false,
                          TestThreadType.TestThreadDispatcher,
                          TestThreadType.TestThreadDispatcher,
                          TestThreadType.TestThreadDispatcher,
                          TestThreadType.TestThreadDispatcher));
      }

      [TestMethod, AsynchronousTest]
      public void If_Sync_UISendAndReceive_From_TP_Should_Follow_Sequence_TP_TTD_TTD_TTD()
      {
         EnqueueAction(() =>
                       RunSchedulerScenarioFromThreadPool(new TaskFactory(TaskScheduler.ForUISendAndReceive()),
                          false,
                          TestThreadType.ThreadPool,
                          TestThreadType.TestThreadDispatcher,
                          TestThreadType.TestThreadDispatcher,
                          TestThreadType.TestThreadDispatcher));
      }

      [TestMethod, AsynchronousTest]
      public void If_ASync_UISendAndReceive_From_TP_Should_Follow_Sequence_TP_TP_TP_TTD()
      {
         EnqueueAction(() =>
                       RunSchedulerScenarioFromThreadPool(new TaskFactory(TaskScheduler.ForUISendAndReceive()),
                          true,
                          TestThreadType.ThreadPool,
                          TestThreadType.ThreadPool,
                          TestThreadType.ThreadPool,
                          TestThreadType.TestThreadDispatcher));
      }

      [TestMethod, AsynchronousTest]
      public void If_Async_UIReceive_From_TTD_Should_Follow_Sequence_TTD_TP_TP_TTD()
      {
         EnqueueAction(() =>
                       RunSchedulerScenarioFromDispatcher(new TaskFactory(TaskScheduler.ForUIReceive()),
                          true,
                          TestThreadType.TestThreadDispatcher,
                          TestThreadType.ThreadPool,
                          TestThreadType.ThreadPool,
                          TestThreadType.TestThreadDispatcher));
      }

      [TestMethod, AsynchronousTest]
      public void If_Sync_UIReceive_From_TTD_Should_Follow_Sequence_TTD_TP_TP_TTD()
      {
         EnqueueAction(() =>
                       RunSchedulerScenarioFromDispatcher(new TaskFactory(TaskScheduler.ForUIReceive()), 
                          false,
                          TestThreadType.TestThreadDispatcher,
                          TestThreadType.ThreadPool,
                          TestThreadType.ThreadPool,
                          TestThreadType.TestThreadDispatcher));
      }

      [TestMethod, AsynchronousTest]
      public void If_Sync_UIReceive_From_TP_Should_Follow_Sequence_TP_TP_TP_TTD()
      {
         EnqueueAction(() =>
                       RunSchedulerScenarioFromThreadPool(new TaskFactory(TaskScheduler.ForUIReceive()),
                          false,
                          TestThreadType.ThreadPool,
                          TestThreadType.ThreadPool,
                          TestThreadType.ThreadPool,
                          TestThreadType.TestThreadDispatcher));
      }

      [TestMethod, AsynchronousTest]
      public void If_Async_UIReceive_From_TP_Should_Follow_Sequence_TP_TP_TP_TTD()
      {
         EnqueueAction(() =>
                       RunSchedulerScenarioFromThreadPool(new TaskFactory(TaskScheduler.ForUIReceive()),
                          true,
                          TestThreadType.ThreadPool,
                          TestThreadType.ThreadPool,
                          TestThreadType.ThreadPool,
                          TestThreadType.TestThreadDispatcher));
      }

      [TestMethod, AsynchronousTest]
      public void If_Async_UISend_From_TTD_Should_Follow_Sequence_TTD_TP_TP_TP()
      {
         EnqueueAction(() =>
                       RunSchedulerScenarioFromDispatcher(new TaskFactory(TaskScheduler.ForUISend()),
                          true,
                          TestThreadType.TestThreadDispatcher,
                          TestThreadType.ThreadPool,
                          TestThreadType.ThreadPool,
                          TestThreadType.ThreadPool));
      }

      [TestMethod, AsynchronousTest]
      public void If_Sync_UISend_From_TTD_Should_Follow_Sequence_TTD_TTD_TTD_TTD()
      {
         EnqueueAction(() =>
                       RunSchedulerScenarioFromDispatcher(new TaskFactory(TaskScheduler.ForUISend()),
                          false,
                          TestThreadType.TestThreadDispatcher,
                          TestThreadType.TestThreadDispatcher,
                          TestThreadType.TestThreadDispatcher,
                          TestThreadType.TestThreadDispatcher));
      }

      [TestMethod, AsynchronousTest]
      public void If_Sync_UISend_From_TP_Should_Follow_Sequence_TP_TPD_TPD_TTD()
      {
         EnqueueAction(() =>
                       RunSchedulerScenarioFromThreadPool(new TaskFactory(TaskScheduler.ForUISend()),
                          false,
                          TestThreadType.ThreadPool,
                          TestThreadType.TestThreadDispatcher,
                          TestThreadType.TestThreadDispatcher,
                          TestThreadType.TestThreadDispatcher));
      }

      [TestMethod, AsynchronousTest]
      public void If_ASync_UISend_From_TP_Should_Follow_Sequence_TP_TP_TP_TP()
      {
         EnqueueAction(() =>
                       RunSchedulerScenarioFromThreadPool(new TaskFactory(TaskScheduler.ForUISend()),
                          true,
                          TestThreadType.ThreadPool,
                          TestThreadType.ThreadPool,
                          TestThreadType.ThreadPool,
                          TestThreadType.ThreadPool));
      }

      private void EnqueueAndWait(bool startOnThreadPool, TaskFactory factory, Action action, string name, bool isAsync,
         Action<TaskDebugger> completed)
      {
         var done = false;

         // enqueue debugger, it is important it is run on the test dispatcher thread
         TaskDebugger taskDebugger = null;
         if (startOnThreadPool)
         {
            ThreadPool.QueueUserWorkItem(
               s =>
                  {
                     taskDebugger = new TaskDebugger(factory, action, name, isAsync);
                     taskDebugger.Start(r => done = true);
                  });
         }
         else
         {
            EnqueueAction(
               () =>
                  {
                     taskDebugger = new TaskDebugger(factory, action, name, isAsync);
                     taskDebugger.Start(s => done = true);
                  });
         }

         // wait for task to complete
         EnqueueWait(() =>
            {
               if (done)
                  completed(taskDebugger);
               return done;
            });
      }

      private void RunSchedulerScenarioFromThreadPool(TaskFactory factory, bool isAsync,
         TestThreadType? expectedStartedOn,
         TestThreadType? expectedRanOn,
         TestThreadType? expectedEndedOn,
         TestThreadType? expectedCalledBackOn)
      {
         RunSchedulerScenario(true, factory, isAsync,
            expectedStartedOn, expectedRanOn, expectedEndedOn, expectedCalledBackOn);
      }

      private void RunSchedulerScenarioFromDispatcher(TaskFactory factory, bool isAsync,
         TestThreadType? expectedStartedOn,
         TestThreadType? expectedRanOn,
         TestThreadType? expectedEndedOn,
         TestThreadType? expectedCalledBackOn)
      {
         RunSchedulerScenario(true, factory, isAsync,
            expectedStartedOn, expectedRanOn, expectedEndedOn, expectedCalledBackOn);
      }

      private void RunSchedulerScenario(bool startOnThreadPool, TaskFactory factory, bool isAsync,
         TestThreadType? expectedStartedOn,
         TestThreadType? expectedRanOn,
         TestThreadType? expectedEndedOn,
         TestThreadType? expectedCalledBackOn)
      {
         // set up action
         EnqueueAndWait(startOnThreadPool, factory, MockDelegates.DoNothing, "Action", isAsync,
            (debugger) => EnqueueAction(
                             () =>
                                {
                                   if (expectedRanOn != null)
                                      debugger.ExpectCalledBackOn(expectedStartedOn.Value);
                                   if (expectedRanOn != null)
                                      debugger.ExpectRanOn(expectedRanOn.Value);
                                   if (expectedEndedOn != null)
                                      debugger.ExpectEndedOn(expectedEndedOn.Value);
                                   if (expectedCalledBackOn != null)
                                      debugger.ExpectCalledBackOn(expectedCalledBackOn.Value);
                                   Debug.WriteLine(debugger.ToString());

                                   debugger.AssertCompleted();

                                   // done
                                   EnqueueCompleted();
                                }));
      }
   }
}